/*
 * Solo - A small and beautiful blogging system written in Java.
 * Copyright (c) 2010-present, b3log.org
 *
 * Solo is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.b3log.solo.processor;

import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import jodd.net.MimeTypes;
import lombok.SneakyThrows;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.b3log.latke.Keys;
import org.b3log.latke.http.FileUpload;
import org.b3log.latke.http.Request;
import org.b3log.latke.http.RequestContext;
import org.b3log.latke.ioc.Inject;
import org.b3log.latke.ioc.Singleton;
import org.b3log.latke.repository.Transaction;
import org.b3log.latke.repository.annotation.Transactional;
import org.b3log.latke.util.Strings;
import org.b3log.solo.model.Common;
import org.b3log.solo.repository.FileRepository;
import org.b3log.solo.util.Images;
import org.b3log.solo.util.Solos;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * File fetch upload processor. 第三方图床自动替换为社区图床 https://github.com/88250/solo/issues/114
 *
 * @author <a href="http://88250.b3log.org">Liang Ding</a>
 * @version 1.0.0.0, Apr 30, 2020
 * @since 4.1.0
 */
@Singleton
public class FetchUploadProcessor {

    @Inject
    private FileRepository fileRepository;

    private static List<String> suffix = Arrays.asList("A", "B", "C");

    /**
     * Logger.
     */
    private static final Logger LOGGER = LogManager.getLogger(FetchUploadProcessor.class);
    public static final String BASE_PATH = "..";

    /**
     * Fetches the remote file and upload it.
     *
     * @param context the specified context
     */
    public void fetchUpload(final RequestContext context) {
        final JSONObject result = Solos.newFail();
        context.renderJSONPretty(result);
        final JSONObject data = new JSONObject();

        final JSONObject requestJSONObject = context.requestJSON();
        final String originalURL = requestJSONObject.optString(Common.URL);
        if (!Strings.isURL(originalURL) || !StringUtils.startsWithIgnoreCase(originalURL, "http")) {
            return;
        }

        if (Images.uploaded(originalURL)) {
            return;
        }

        String contentType;
        String tmpFile;
        String md5Hex;
        try {

            /**
             * 生成临时文件并计算md5
             */
            String tp = BASE_PATH + File.separator + "resources" + File.separator + "tmp" + File.separator;
            File file = new File(tp);
            if (!file.exists()) {
                file.mkdirs();
            }
            tmpFile = tp + System.currentTimeMillis() + ".tmp";

            HttpUtil.downloadFile(originalURL, new File(tmpFile));
            md5Hex = SecureUtil.md5(tmpFile);

            contentType = FileUtil.getMimeType(originalURL);
        } catch (final Exception e) {
            LOGGER.log(Level.ERROR, "Fetch file [url=" + originalURL + "] failed", e);
            result.put(Keys.MSG, "Fetch file [url=" + originalURL + "] failed");
            return;
        }

        try {

            LocalDateTime localDateTime = LocalDateTime.now().plusHours(8);
            String filename = FilenameUtils.getName(originalURL);
            if (filename.lastIndexOf("?") != -1) {
                filename = filename.substring(0, filename.lastIndexOf("?"));
            }
            String fn = filename;
            String suffix = getSuffix(contentType);
            String resourcePath = getResourcePath(suffix);

            final File file = new File(BASE_PATH + resourcePath);
            JSONObject speedFile = fileRepository.getByMd5(md5Hex);

            String replace = resourcePath.replace(File.separator, "/");
            data.put(Common.URL, replace);

            final Transaction transaction = fileRepository.beginTransaction();
            if (speedFile == null) {
                // 保存文件和数据
                FileUtil.move(new File(tmpFile),file,true);
                //todo 存入数据库
                JSONObject fileBean = new JSONObject();
                fileBean.put("path", file.getCanonicalPath());
                fileBean.put("url", replace);
                fileBean.put("suffer", suffix);
                fileBean.put("filename", filename);
                fileBean.put("createTime", LocalDateTime.now());

                fileBean.put("articleId", "last_" + localDateTime.toEpochSecond(ZoneOffset.of("+8")));
                fileBean.put("md5", md5Hex);

                fileRepository.addNewFile(fileBean);

                data.put("succMap", new HashMap() {{
                    put(fn, replace);
                }});
            } else {
                // 删除临时文件
                FileUtil.del(tmpFile);
                // 只更新数据
                speedFile.put("filename", filename);
                speedFile.put("url", replace);
                speedFile.put("createTime", LocalDateTime.now());
                speedFile.put("articleId", "last_" + localDateTime.toEpochSecond(ZoneOffset.of("+8")));
                speedFile.remove("oId");
                fileRepository.addNewFile(speedFile);
                data.put("succMap", new HashMap() {{
                    put(fn, speedFile.get("url"));
                }});
            }
            transaction.commit();


        } catch (final Exception e) {
            final String msg = "Upload file to community OSS [url=" + originalURL + "] failed";
            LOGGER.log(Level.ERROR, msg, e);
            result.put(Keys.MSG, msg);
            return;
        }


        data.put("originalURL", originalURL);

        result.put(Common.DATA, data);
        result.put(Keys.CODE, 0);
        result.put(Keys.MSG, "");
    }

    /**
     * Gets suffix (for example jpg) with the specified content type.
     *
     * @param contentType the specified content type
     * @return suffix
     */
    public static String getSuffix(final String contentType) {
        String ret;
        final String[] exts = MimeTypes.findExtensionsByMimeTypes(contentType, false);
        if (null != exts && 0 < exts.length) {
            ret = exts[0];
        } else {
            ret = StringUtils.substringAfter(contentType, "/");
            ret = StringUtils.substringBefore(ret, ";");
        }

        return ret;
    }

    /**
     * 創建臨時文件
     *
     * @param suffix 後綴
     */
    public static String getResourcePath(String suffix) {
        String folder = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy" + File.separator + "MM" + File.separator + "dd" + File.separator));
        String name = UUID.randomUUID().toString().replace("-", "");
        return File.separator + "resources" + File.separator + folder + name + "." + suffix;
    }

    /**
     * 本地上传文件
     *
     * @param context the specified context
     */
    @SneakyThrows
    public void clientUpload(final RequestContext context) {
        Request request = context.getRequest();
        List<FileUpload> fileUploads = request.getFileUploads("file[]");
        FileUpload fileUpload = fileUploads.get(0);


        final JSONObject result = Solos.newFail();
        context.renderJSONPretty(result);

        String filename = fileUpload.getFilename();
        final JSONObject data = getJsonObject(filename, fileUpload.getData());

        result.put(Common.DATA, data);
        result.put(Keys.CODE, 0);
        result.put(Keys.MSG, "");
    }

    @NotNull
    @Transactional
    public JSONObject getJsonObject(String filename, byte[] filebyte) throws IOException {
        String md5Hex = DigestUtils.md5Hex(filebyte);
        String suffix = filename.substring(filename.lastIndexOf(".") + 1);
        String resourcePath = getResourcePath(suffix);

        final File file = new File(BASE_PATH + resourcePath);

        JSONObject speedFile = fileRepository.getByMd5(md5Hex);

        final JSONObject data = new JSONObject();
        data.put("errFiles", new String[]{});
        LocalDateTime localDateTime = LocalDateTime.now().plusHours(8);

        if (speedFile == null) {
            // 保存文件和数据
            FileUtils.writeByteArrayToFile(file, filebyte);
            // 存入数据库
            JSONObject fileBean = new JSONObject();
            fileBean.put("path", file.getCanonicalPath());
            fileBean.put("url", resourcePath.replace(File.separator, "/"));
            fileBean.put("suffer", suffix);
            fileBean.put("filename", filename);
            fileBean.put("createTime", LocalDateTime.now());

            fileBean.put("articleId", "last_" + localDateTime.toEpochSecond(ZoneOffset.of("+8")));
            fileBean.put("md5", md5Hex);

            fileRepository.addNewFile(fileBean);

            data.put("succMap", new HashMap() {{
                put(filename, resourcePath.replace(File.separator, "/"));
            }});
        } else {
            // 只更新数据
            speedFile.put("filename", filename);
            speedFile.put("url", resourcePath.replace(File.separator, "/"));
            speedFile.put("createTime", LocalDateTime.now());
            speedFile.put("suffer", suffix);
            speedFile.put("articleId", "last_" + localDateTime.toEpochSecond(ZoneOffset.of("+8")));
            speedFile.remove("oId");
            fileRepository.addNewFile(speedFile);
            data.put("succMap", new HashMap() {{
                put(filename, speedFile.get("url"));
            }});
        }
        return data;
    }


    /**
     * @author liGuanglei
     * @description 清理过期文件
     * @date 2023/11/2 21:29
     **/
    @SneakyThrows
    @Transactional
    public void cleanFile(RequestContext requestContext) {
        List<JSONObject> outTimeList = fileRepository.getOutTimeList();

        if (outTimeList.size() > 0) {
            List<String> collect = outTimeList.stream().map(m -> m.getString("path")).distinct().collect(Collectors.toList());


//            fileRepository.executeSql(String.format("delete from b3_solo_files where oId in ('%s')", oids));


            String oids = outTimeList.stream().map(m -> m.getString("oId")).collect(Collectors.joining("','"));
            fileRepository.executeSql(String.format("delete from b3_solo_files where oId in ('%s')", oids));
        }

    }

    @SneakyThrows
    public static void main(String[] args) {
//        List<String> list = Arrays.asList("A", "B", "C");
//        System.out.println("list.contains(\"A\") = " + list.contains("A"));
//        String originalURL = "https://img-blog.csdnimg.cn/20200421211755384.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0MzQzNTg0,size_16,color_FFFFFF,t_70#pic_center";
//        String name = FilenameUtils.getName(originalURL);
//        System.out.println("name = " + name);


        String url = "https://down.cnwans.com/wp-content/uploads/2021/03/QQ%E5%9B%BE%E7%89%8720201231193623.jpg";
        byte[] bytes;
        bytes = HttpUtil.get(url, 5000).getBytes();
        String mimeType = FileUtil.getMimeType(url);
        HttpUtil.downloadFile(url, "D:\\Work_space\\resources\\2024\\03\\30");

        System.out.println(DigestUtils.md5Hex(bytes));
//        FileUtils.writeByteArrayToFile(new File("D:\\Work_space\\resources\\2024\\03\\30\\1.jpg"), bytes);


        File file = new File("D:\\Work_space\\resources\\2024\\03\\30\\1.jpg");
        if (file.exists()) {
            file.delete();
        }
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(bytes, 0, bytes.length);
        fos.flush();
        fos.close();


        System.out.println(mimeType);
    }
}
